package com.econ.powercloud.ems.service.biz;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.econ.powercloud.common.base.facade.enums.StatusType;
import com.econ.powercloud.common.base.facade.exception.CommonRuntimeException;
import com.econ.powercloud.common.base.facade.pojo.MsgResult;
import com.econ.powercloud.common.base.facade.pojo.PageResult;
import com.econ.powercloud.common.base.facade.util.JacksonUtil;
import com.econ.powercloud.ems.common.enums.*;
import com.econ.powercloud.ems.common.pojo.EmsKafkaEvenDataDTO;
import com.econ.powercloud.ems.config.EMSStaticMap;
import com.econ.powercloud.ems.constant.SysConstant;
import com.econ.powercloud.ems.dal.entity.*;
import com.econ.powercloud.ems.pojo.ApiDataVO;
import com.econ.powercloud.ems.pojo.ProtectSoeEventRecordVO;
import com.econ.powercloud.ems.pojo.SectionDataVO;
import com.econ.powercloud.ems.pojo.requestVO.ProtectSoeEventRequestVO;
import com.econ.powercloud.ems.rpc.IFeignPowerNodeConsoleClient;
import com.econ.powercloud.ems.service.dao.*;
import com.econ.powercloud.ems.util.NetUtils;
import com.econ.powercloud.ems.util.RedisCacheUtil;
import com.google.common.collect.Lists;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author : zjf
 * @Description : 保护事件/SOE事件service
 * @Date : 2022/8/3 15:19
 * @Modified by : zjf
 */
@Service
public class ProtectAndSoeEventService {
    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");

    @Autowired
    private EmsProtectSoeEventRecordDAO protectSoeEventRecordDAO;
    @Autowired
    private EmsEnergyStationDAO emsEnergyStationDAO;
    @Autowired
    private EmsEnergyEquipmentDAO emsEnergyEquipmentDAO;
    @Autowired
    private EmsDeviceDAO emsDeviceDAO;
    @Autowired
    private IFeignPowerNodeConsoleClient iFeignPowerNodeConsoleClient;
    @Autowired
    private EmsProtectSoeConfigDAO emsProtectSoeConfigDAO;
    @Autowired
    private RedisCacheUtil redisCacheUtil;
    @Autowired
    private ExtIotNodeDAO extIotNodeDAO;

    /**
     * 获取保护事件/SOE事件列表
     */
    public PageResult<List<ProtectSoeEventRecordVO>> getList(ProtectSoeEventRequestVO requestVO,Integer pageNo,Integer pageSize){
        List<String> deviceCodeList = Lists.newArrayList();
        if(requestVO.getDeviceCode() == null){
            List<EmsDevice> deviceList = emsDeviceDAO.getListByCondition(null,requestVO.getEquipmentId(), DeviceType.CHILD_PROTECT_DEVICE.getCode());
            deviceCodeList.addAll(deviceList.stream().map(EmsDevice::getDeviceCode).collect(Collectors.toList()));
        }else {
            deviceCodeList.add(requestVO.getDeviceCode());
        }
        Page<EmsProtectSoeEventRecord> page = protectSoeEventRecordDAO.selectListByCondition(requestVO.getEventType(),deviceCodeList,requestVO.getPhaseType(),requestVO.getActionType(),
                requestVO.getSoeEventName(),requestVO.getSoeStatus(),requestVO.getStartTime(),requestVO.getEndTime(),pageNo,pageSize);
        if(CollectionUtils.isEmpty(page.getRecords())){
            return PageResult.<List<ProtectSoeEventRecordVO>>builder().totalCount(0).pageNo(pageNo).data(Collections.emptyList()).build();
        }
        List<ProtectSoeEventRecordVO> recordVOList = JacksonUtil.convertToList(page.getRecords(),ProtectSoeEventRecordVO.class);
        initProtectSoeEventDataList(recordVOList);
        return PageResult.<List<ProtectSoeEventRecordVO>>builder().totalCount((int)page.getTotal()).pageNo(pageNo).data(recordVOList).build();
    }

    /**
     * 获取单个记录详情
     * @param id
     * @return
     */
    public ProtectSoeEventRecordVO get(Integer id){
        ProtectSoeEventRecordVO result = JacksonUtil.convertToObj(protectSoeEventRecordDAO.getById(id),ProtectSoeEventRecordVO.class);
        initProtectSoeEventDataList(Collections.singletonList(result));
        return result;
    }

    /**
     * 召唤单个保护事件的断面数据
     */
    public Integer calSectionDataById(Integer id){
        EmsProtectSoeEventRecord eventRecord = protectSoeEventRecordDAO.getById(id);
        Assert.notNull(eventRecord, FaultDesc.FAILURE_INCONFORMITY.getDesc());
        Assert.isTrue(Objects.equals(ProtectAndSoeEventType.PROTECT_EVENT.getCode(),eventRecord.getEventType()),"仅保护事件需要召唤断面数据！");
        Assert.isNull(eventRecord.getSectionContent(),"该事件已存在断面数据,无需重复召唤!");
        EmsDevice emsDevice = emsDeviceDAO.selectByDeviceCode(eventRecord.getDeviceCode());
        //检查设备是否挂牌检修(检修则不下发)
        Integer listingType = extIotNodeDAO.selectByNodeId(emsDevice.getDeviceCode()).getListingType();
        Assert.isTrue(!ListingTypeEnum.OVERHAUL.getCode().equals(listingType),"设备检修中");
        MsgResult<Integer> result = iFeignPowerNodeConsoleClient.callHisPg(NetUtils.getClientIp(),emsDevice.getInitDeviceCode(),eventRecord.getEventNumber(),eventRecord.getEventNumber());
        if(StatusType.OK.getCode() != result.getStatus()){
            throw new CommonRuntimeException(StatusType.FAIL,"召唤历史断面数据失败!");
        }
        return result.getData();
    }

    /**
     * 召唤保护事件的断面数据，每次最多召唤100条数据
     */
    public void calProtectEvent(){
        //获取设备信息
        Map<String,EmsDevice> deviceMap = emsDeviceDAO.getListByCondition(null,null,DeviceType.CHILD_PROTECT_DEVICE.getCode()).stream().collect(Collectors.toMap(EmsDevice::getDeviceCode,d->d,(p1,p2)->p1));
        //获取保护事件中的每一个设备的最新一条记录
        Map<String,EmsProtectSoeEventRecord> recordMap = protectSoeEventRecordDAO.selectLeastRecordWithDeviceByCondition(ProtectAndSoeEventType.PROTECT_EVENT.getCode());
        if(MapUtils.isEmpty(recordMap)){
            return;
        }
        recordMap.values().stream().forEach(d->{
            EmsDevice emsDevice = deviceMap.get(d.getDeviceCode());
            //检查设备是否挂牌检修(检修则不下发)
            Integer listingType = extIotNodeDAO.selectByNodeId(emsDevice.getDeviceCode()).getListingType();
            Assert.isTrue(!ListingTypeEnum.OVERHAUL.getCode().equals(listingType),"设备检修中");
            Integer start = d.getEventNumber() > 100?d.getEventNumber()-100 : 0;
            MsgResult<Integer> result = iFeignPowerNodeConsoleClient.callHisPg(NetUtils.getClientIp(),emsDevice.getInitDeviceCode(),start,d.getEventNumber());
            if(StatusType.OK.getCode() != result.getStatus()){
                throw new CommonRuntimeException(StatusType.FAIL,"召唤历史断面数据失败!");
            }
        });
    }

    /**
     * 召唤保护事件的断面数据，每次最多召唤100条数据
     */
    public void calSoeEvent(){
        //获取设备信息
        Map<String,EmsDevice> deviceMap = emsDeviceDAO.getListByCondition(null,null,DeviceType.CHILD_PROTECT_DEVICE.getCode()).stream().collect(Collectors.toMap(EmsDevice::getDeviceCode,d->d,(p1,p2)->p1));
        //获取保护事件中的每一个设备的最新一条记录
        Map<String,EmsProtectSoeEventRecord> recordMap = protectSoeEventRecordDAO.selectLeastRecordWithDeviceByCondition(ProtectAndSoeEventType.SOE_EVENT.getCode());
        if(MapUtils.isEmpty(recordMap)){
            return;
        }
        recordMap.values().stream().forEach(d->{
            EmsDevice emsDevice = deviceMap.get(d.getDeviceCode());
            Integer start = d.getEventNumber() > 100?d.getEventNumber()-100 : 0;
            MsgResult<Integer> result = iFeignPowerNodeConsoleClient.callHisSoe(NetUtils.getClientIp(),emsDevice.getInitDeviceCode(),start,d.getEventNumber());
            if(StatusType.OK.getCode() != result.getStatus()){
                throw new CommonRuntimeException(StatusType.FAIL,"召唤历史数据失败!");
            }
        });
    }

    /**
     * 获取相应的下拉框
     */
    public List<ApiDataVO> getConfigByCondition(String deviceCode,ProtectAndSoeFiledEnum filedEnum){
        EmsDevice emsDevice = emsDeviceDAO.selectByDeviceCode(deviceCode);
        List<EmsProtectSoeConfig> emsProtectSoeConfigs = emsProtectSoeConfigDAO.getListByInitDeviceCodeAndType(emsDevice.getInitDeviceCode(),filedEnum.getCode());
        if(CollectionUtils.isEmpty(emsProtectSoeConfigs)){
            return Collections.emptyList();
        }
        return emsProtectSoeConfigs.stream()
                .map(d -> new ApiDataVO(d.getVal(), d.getValName())).collect(Collectors.toList());
    }

    /**
     * 解析保护事件/SOE事件
     */
    public void dealWithProtectAndSoeEvent(EmsKafkaEvenDataDTO data, EmsDevice emsDevice, EmsProtectSoePidConfig pidConfig){
        try {
            if(Objects.equals(ProtectAndSoeEventType.PROTECT_EVENT.getCode(),pidConfig.getType())){
                this.dealWithProtectEvent(data,emsDevice);
            }else if(Objects.equals(ProtectAndSoeEventType.SOE_EVENT.getCode(),pidConfig.getType())){
                this.dealWithSOEEvent(data,emsDevice);
            }
        }catch (Exception ex){
            throw new CommonRuntimeException(StatusType.FAIL,String.format("保护事件/SOE事件解析失败,数据:%s", JSON.toJSON(data)));
        }
    }

    private void initProtectSoeEventDataList(List<ProtectSoeEventRecordVO> recordVOList){
        if(CollectionUtils.isEmpty(recordVOList)){
            return;
        }
        List<String> deviceCodeList = recordVOList.stream().map(ProtectSoeEventRecordVO::getDeviceCode).collect(Collectors.toList());
        Map<String,EmsDevice> deviceMap = emsDeviceDAO.selectByDeviceCodes(deviceCodeList).stream().collect(Collectors.toMap(EmsDevice::getDeviceCode,d->d,(p1,p2)->p1));
        Map<String,String> equipmentIdToNameMap = emsEnergyEquipmentDAO.list().stream().collect(Collectors.toMap(EmsEnergyEquipment::getEnergyEquipmentId,EmsEnergyEquipment::getEnergyEquipmentName));
        Map<String,String> stationIdToNameMap = emsEnergyStationDAO.list().stream().collect(Collectors.toMap(EmsEnergyStation::getEnergyStationId,EmsEnergyStation::getEnergyStationName));
        recordVOList.stream().forEach(d->{
            EmsDevice emsDevice = deviceMap.get(d.getDeviceCode());
            d.setDeviceName(emsDevice.getDeviceName());
            d.setEquipmentId(emsDevice.getEnergyEquipmentId());
            d.setEquipmentName(equipmentIdToNameMap.get(emsDevice.getEnergyEquipmentId()));
            d.setStationId(emsDevice.getEnergyStationId());
            d.setSoeStatusName(stationIdToNameMap.get(emsDevice.getEnergyStationId()));
            if(StringUtils.isNotBlank(d.getSectionContent())){
                d.setSectionList(JSON.parseArray(d.getSectionContent(),SectionDataVO.class));
            }
        });
    }

    /**
     * 处理保护事件逻辑
     */
    private void dealWithProtectEvent(EmsKafkaEvenDataDTO data, EmsDevice emsDevice) throws Exception{
        //判断原始数据是否为空
        if(data.getOriginData() == null){
            return;
        }
        Map<String, Object> value = data.getOriginData().getParams().getValue();
        //动作类别
        Integer actionType = (Integer) value.get(ProtectAndSoeFiledEnum.ATYPE.getTag());
        //创建时间
        Long gmtCreate = sdf.parse(value.get(ProtectAndSoeFiledEnum.ICE.getTag()).toString()).getTime();
        //根据设备code,动作类型,创建时间来判断该事件是否存在
//        EmsProtectSoeEventRecord eventRecord = protectSoeEventRecordDAO.selectOneByCondition(ProtectAndSoeEventType.PROTECT_EVENT.getCode(),
//                emsDevice.getDeviceCode(),actionType,null,gmtCreate);
        EmsProtectSoeEventRecord eventRecord = redisCacheUtil.selectLeastProtectEvent(emsDevice.getDeviceCode(), actionType, gmtCreate);
        //不为空,更新
        if(eventRecord != null){
            if(eventRecord.getSectionContent() != null){
                return;
            }
            //解析断面数据
            String sectionContent = this.getSectionData(emsDevice.getInitDeviceCode(),value);
            eventRecord.setSectionContent(sectionContent);
            protectSoeEventRecordDAO.updateById(eventRecord);
            redisCacheUtil.deleteProtectEvent(emsDevice.getDeviceCode(), actionType, gmtCreate);
            return;
        }
        Integer eventNumber = (Integer) value.get(ProtectAndSoeFiledEnum.EID.getTag());
        Integer phaseType = (Integer) value.get(ProtectAndSoeFiledEnum.PHASE.getTag());
        String actionVal = value.get(ProtectAndSoeFiledEnum.VAL.getTag()).toString();
        String sectionContent = this.getSectionData(emsDevice.getInitDeviceCode(),value);
        //为空,新增记录
        EmsProtectSoeEventRecord insertRecord = EmsProtectSoeEventRecord.builder()
                .eventType(ProtectAndSoeEventType.PROTECT_EVENT.getCode())
                .eventNumber(eventNumber).deviceCode(emsDevice.getDeviceCode())
                .phaseType(phaseType).actionType(actionType)
                .phaseTypeName(getTypeNameByCondition(emsDevice.getInitDeviceCode(),ProtectAndSoeFiledEnum.PHASE, phaseType))
                .actionTypeName(getTypeNameByCondition(emsDevice.getInitDeviceCode(),ProtectAndSoeFiledEnum.ATYPE, actionType))
                .actionVal(actionVal).originalContent(JSON.toJSONString(data.getOriginData()))
                .sectionContent(sectionContent)
                .gmtCreate(gmtCreate)
                .build();
        protectSoeEventRecordDAO.save(insertRecord);
        redisCacheUtil.addProtectEvent(emsDevice.getDeviceCode(), actionType, gmtCreate,insertRecord);
    }

    /**
     * 处理SOE事件逻辑
     */
    private void dealWithSOEEvent(EmsKafkaEvenDataDTO data, EmsDevice emsDevice) throws Exception{
        //判断原始数据是否为空
        if(data.getOriginData() == null){
            return;
        }
        Map<String, Object> value = data.getOriginData().getParams().getValue();
        //SOE事件
        Integer soeEvent = (Integer) value.get(ProtectAndSoeFiledEnum.CH.getTag());
        //创建时间
        Long gmtCreate = sdf.parse(value.get(ProtectAndSoeFiledEnum.ICE.getTag()).toString()).getTime();
        //根据设备code,动作类型,创建时间来判断该事件是否存在
//        EmsProtectSoeEventRecord eventRecord = protectSoeEventRecordDAO.selectOneByCondition(ProtectAndSoeEventType.PROTECT_EVENT.getCode(),
//                emsDevice.getDeviceCode(),null,soeEvent,gmtCreate);
        EmsProtectSoeEventRecord eventRecord = redisCacheUtil.selectLeastSoeEvent(emsDevice.getDeviceCode(), soeEvent, gmtCreate);
        if(eventRecord != null){
            redisCacheUtil.deleteSoeEvent(emsDevice.getDeviceCode(), soeEvent, gmtCreate);
            return;
        }
        //状态
        Integer soeStatus = (Integer) value.get(ProtectAndSoeFiledEnum.STA.getTag());
        Integer eventNumber = (Integer) value.get(ProtectAndSoeFiledEnum.EID.getTag());
        //为空,新增记录
        EmsProtectSoeEventRecord insertRecord = EmsProtectSoeEventRecord.builder()
                .eventType(ProtectAndSoeEventType.SOE_EVENT.getCode())
                .eventNumber(eventNumber).deviceCode(emsDevice.getDeviceCode())
                .soeEvent(soeEvent).soeStatus(soeStatus)
                .soeEventName(getTypeNameByCondition(emsDevice.getInitDeviceCode(),ProtectAndSoeFiledEnum.CH, soeEvent))
                .soeStatusName(SoeStatusType.getCodeFromCode(soeStatus))
                .originalContent(JSON.toJSONString(data.getOriginData()))
                .gmtCreate(gmtCreate)
                .build();
        protectSoeEventRecordDAO.save(insertRecord);
        redisCacheUtil.addSoeEvent(emsDevice.getDeviceCode(), soeEvent, gmtCreate,insertRecord);
    }

    /**
     * 获取一些特定字段下的值对应的中文名称
     */
    private String getTypeNameByCondition(String initDeviceCode,ProtectAndSoeFiledEnum filedEnum,Integer val){
        Map<String, EmsProtectSoeConfig> protectSoeConfigMap = EMSStaticMap.protectSoeConfigMap.get(initDeviceCode);
        if(MapUtils.isEmpty(protectSoeConfigMap)){
            return null;
        }
        EmsProtectSoeConfig protectSoeConfig = protectSoeConfigMap.get(filedEnum.getCode() + SysConstant.CABLE + val);
        if(protectSoeConfig == null){
            return null;
        }
        return protectSoeConfig.getValName();
    }

    /**
     * 处理断面数据,返回JSON格式的字符串
     */
    private String getSectionData(String initDeviceCode,Map<String, Object> value){
        List<SectionDataVO> list = Lists.newArrayList();
        List<EmsProtectSectionConfig> configList = EMSStaticMap.protectSectionConfigMap.get(initDeviceCode);
        if(CollectionUtils.isEmpty(configList)){
            return null;
        }
        configList.stream().forEach(d->{
            Object object = value.get(d.getField());
            if(object != null){
                list.add(SectionDataVO.builder().field(d.getField()).filedName(d.getFiledName()).val(object.toString()).build());
            }
        });
        if(CollectionUtils.isEmpty(list)){
            return null;
        }
        return JSON.toJSONString(list);
    }
}
